package craky.component;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseMotionAdapter;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.JRootPane;
import javax.swing.UIManager;

import com.abigdreamer.swing.component.UIResourceManager;

public class JLoadingGlassPane extends JComponent
  implements ActionListener
{
  private static final long serialVersionUID = -2161086706185983717L;
  private JProgressBar progressBar;
  private JLabel lbInfo;
  private JButton btnCancel;
  private String info;
  private boolean cancelButtonVisible;
  private boolean cancelled;
  private ScheduledThreadPoolExecutor executor;
  private ScheduledFuture<?> future;
  private Action finishedAction;
  private Action cancelAction;
  private float alpha;
  private byte[] lock;

  public JLoadingGlassPane()
  {
    this(null);
  }

  public JLoadingGlassPane(JRootPane owner)
  {
    this(owner, true, true);
  }

  public JLoadingGlassPane(JRootPane owner, boolean indeterminate, boolean cancelButtonVisible)
  {
    this(owner, indeterminate, cancelButtonVisible, null);
  }

  public JLoadingGlassPane(JRootPane owner, boolean indeterminate, boolean cancelButtonVisible, String info)
  {
    if (owner != null)
    {
      owner.setGlassPane(this);
    }

    init();
    this.progressBar.setIndeterminate(indeterminate);
    setCancelButtonVisible(cancelButtonVisible);
    setInfo(info);
  }

  private void init()
  {
    this.progressBar = new JRound3DProgressBar();
    this.lbInfo = new JLabel();
    this.btnCancel = new JButton(UIManager.getString("OptionPane.cancelButtonText"));
    this.lock = new byte[0];
    this.alpha = 0.65F;

    this.lbInfo.setVerticalAlignment(3);
    this.lbInfo.setHorizontalAlignment(2);
    this.progressBar.setPreferredSize(new Dimension(-1, 12));
    this.btnCancel.setPreferredSize(new Dimension(-1, 22));
    this.btnCancel.addActionListener(this);
    setOpaque(false);
    setFont(new Font("Dialog", 1, 15));
    setVisible(false);
    setBackground(UIResourceManager.getWhiteColor());
    setForeground(Color.BLUE);
    setFocusTraversalKeysEnabled(false);
    setLayout(null);
    add(this.lbInfo);
    add(this.progressBar);
    add(this.btnCancel);
    addMouseListener(new MouseAdapter()
    {
    });
    addMouseMotionListener(new MouseMotionAdapter()
    {
    });
    addKeyListener(new KeyAdapter()
    {
    });
    addComponentListener(new ComponentAdapter()
    {
      public void componentShown(ComponentEvent evt)
      {
        JLoadingGlassPane.this.requestFocusInWindow();
      }

      public void componentResized(ComponentEvent e)
      {
        JLoadingGlassPane.this.reLayout();
      }
    });
  }

  private void reLayout() {
    int width = getWidth();
    int height = getHeight();
    int buttonHeight = this.btnCancel.getPreferredSize().height;
    int barWidth = width * 2 / 3;
    int barHeight = this.progressBar.getPreferredSize().height;
    int barX = (width - barWidth) / 2;
    int barY = (height - barHeight) / 2;

    this.progressBar.setBounds(barX, barY, barWidth, barHeight);
    this.lbInfo.setBounds(barX, 0, barWidth, barY - 10);

    if (this.cancelButtonVisible)
    {
      this.btnCancel.setBounds(0, height - buttonHeight, width, buttonHeight);
    }
  }

  protected void paintComponent(Graphics g)
  {
    Graphics2D g2d = (Graphics2D)g;
    Composite oldComposite = g2d.getComposite();
    Rectangle clip = g.getClipBounds();
    g2d.setComposite(AlphaComposite.SrcOver.derive(this.alpha));
    g2d.setColor(getBackground());
    g2d.fillRect(clip.x, clip.y, clip.width, clip.height);
    g2d.setComposite(oldComposite);
  }

  public void runCommand(Runnable command)
  {
    runCommand(command, true);
  }

  public void runCommandWithoutLock(Runnable command)
  {
    runCommand(command, false);
  }

  private void runCommand(Runnable command, boolean wait)
  {
    this.cancelled = false;
    Task task = new Task(command, null);
    this.executor = new ScheduledThreadPoolExecutor(1);
    this.future = this.executor.schedule(task, 0L, TimeUnit.MILLISECONDS);
    setVisible(true);

    if (wait)
    {
      synchronized (this.lock)
      {
        try
        {
          this.lock.wait();
        }
        catch (InterruptedException e)
        {
          e.printStackTrace();
        }
      }
    }
  }

  public void stopCommand()
  {
    this.future.cancel(true);
    this.cancelled = true;
  }

  public void actionPerformed(ActionEvent e)
  {
    if (this.cancelAction != null)
    {
      this.cancelAction.actionPerformed(null);
    }
    else
    {
      stopCommand();
    }
  }

  public void setFont(Font font)
  {
    super.setFont(font);
    this.lbInfo.setFont(font);
  }

  public void setForeground(Color color)
  {
    super.setForeground(color);
    this.lbInfo.setForeground(color);
  }

  public float getAlpha()
  {
    return this.alpha;
  }

  public void setAlpha(float alpha)
  {
    if ((alpha >= 0.0F) && (alpha <= 1.0F))
    {
      this.alpha = alpha;
      repaint();
    }
    else
    {
      throw new IllegalArgumentException("Invalid alpha:" + alpha);
    }
  }

  public ActionListener getCancelAction()
  {
    return this.cancelAction;
  }

  public void setCancelAction(Action cancelAction)
  {
    this.cancelAction = cancelAction;
  }

  public Action getFinishedAction()
  {
    return this.finishedAction;
  }

  public void setFinishedAction(Action finishedAction)
  {
    this.finishedAction = finishedAction;
  }

  public JProgressBar getProgressBar()
  {
    return this.progressBar;
  }

  public JButton getCancelButton()
  {
    return this.btnCancel;
  }

  public String getInfo()
  {
    return this.info;
  }

  public void setInfo(String info)
  {
    this.info = info;
    this.lbInfo.setText("<html>" + info);
  }

  public boolean isCancelButtonVisible()
  {
    return this.cancelButtonVisible;
  }

  public void setCancelButtonVisible(boolean cancelButtonVisible)
  {
    this.cancelButtonVisible = cancelButtonVisible;
    this.btnCancel.setVisible(cancelButtonVisible);
  }

  public boolean isCancelled()
  {
    return this.cancelled;
  }

  private class Task<V> extends FutureTask<V>
  {
    public Task(Runnable runnable,V result)
    {
      super(runnable, result);
    }

    protected void done()
    {
      JLoadingGlassPane.this.executor.shutdown();
      JLoadingGlassPane.this.executor = null;
      JLoadingGlassPane.this.future = null;
      JLoadingGlassPane.this.setVisible(false);

      if ((!JLoadingGlassPane.this.cancelled) && (JLoadingGlassPane.this.finishedAction != null))
      {
        JLoadingGlassPane.this.finishedAction.actionPerformed(null);
      }

      synchronized (JLoadingGlassPane.this.lock)
      {
        JLoadingGlassPane.this.lock.notifyAll();
      }
    }
  }
}